﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Sinacor.Infra.UI.FileTransfer.Client.Infra.FileTransfer.FileRepositoryService;
using Sinacor.Infra.UI.Common;
using Sinacor.Infra.UI.Security.Services;
using System.Threading;
using Ionic.Zip;
using PSE.Framework.Common.Security.Services.Configuration;
using System.ServiceModel;

namespace Sinacor.Infra.UI.FileTransfer.Client
{
    /// <summary>
    /// Comportamento de compactação dos arquivos
    /// </summary>
    public enum CompressionBehavior
    {
        /// <summary>
        /// Compactação desabilitada
        /// </summary>
        Disabled,
        /// <summary>
        /// Compactação habilitada
        /// </summary>
        Compress,
        /// <summary>
        /// Entrega o arquivo compactado
        /// </summary>
        DeliverCompressed
    }

    /// <summary>
    /// Localização da pasta
    /// </summary>
    public enum FolderLocation
    {
        /// <summary>
        /// Pasta de upload
        /// </summary>
        Upload,
        /// <summary>
        /// Pasta de download
        /// </summary>
        Download
    }

    public class FileRepository
    {
        private static FileRepository _instance;
        private static Mutex _mutex = new Mutex();

        /// <summary>
        /// Construtor privado para impedir mais de uma instância da classe.
        /// </summary>
        private FileRepository()
        {
        }

        /// <summary>
        /// Retorna uma instância da classe FileRepository
        /// </summary>
        public static FileRepository GetInstance()
        {
            try
            {
                _mutex.WaitOne();

                if (_instance == null)
                    _instance = new FileRepository();
            }
            catch (Exception ex)
            {
                throw new FileRepositoryCommunicationException("An exception ocurred when to get the instance of the class. See the inner exceptions for more details.", ex);
            }
            finally
            {
                _mutex.ReleaseMutex();
            }

            return _instance;
        }

        private CompressionBehavior _uploadCompression;
        /// <summary>
        /// Indica se deve compactar o arquivo antes de efetuar o upload
        /// </summary>
        public CompressionBehavior UploadCompression
        {
            get { return _uploadCompression; }
            set { _uploadCompression = value; }
        }

        private CompressionBehavior _downloadCompression;
        /// <summary>
        /// Indica se deve compactar o arquivo antes de efetuar o download
        /// </summary>
        public CompressionBehavior DownloadCompression
        {
            get { return _downloadCompression; }
            set { _downloadCompression = value; }
        }

        /// <summary>
        /// Método responsável por efetuar o Upload do arquivo
        /// </summary>        
        /// <param name="processName">Nome do processo</param>
        /// <param name="sourcePath">Caminho completo do arquivo</param>
        public void UploadFile(string processName, string sourcePath)
        {
            if (string.IsNullOrEmpty(processName))
                throw new ArgumentNullException("processName");

            if (string.IsNullOrEmpty(sourcePath))
                throw new ArgumentNullException("sourcePath");

            this.UploadFile(processName, sourcePath, Path.GetFileName(sourcePath));
        }

        /// <summary>
        /// Método responsável por efetuar o Upload do arquivo
        /// </summary>        
        /// <param name="processName">Nome do processo</param>
        /// <param name="sourcePath">Caminho completo do arquivo</param>
        /// <param name="destFileName">Nome do arquivo a ser gravado no servidor</param>
        public void UploadFile(string processName, string sourcePath, string destFileName)
        {
            if (string.IsNullOrEmpty(processName))
                throw new ArgumentNullException("processName");

            if (string.IsNullOrEmpty(sourcePath))
                throw new ArgumentNullException("sourcePath");

            if (string.IsNullOrEmpty(destFileName))
                throw new ArgumentNullException("destFileName");

            try
            {
                Session.Session session = Session.SessionManager.GetInstance().GetCurrentSession();

                if (session != null)
                {
                    SessionInformation sessionInfo = session.GenerateSessionInformation();

                    if (sessionInfo != null)
                    {
                        using (ServiceInstance<IFileRepositoryService> si = new ServiceInstance<IFileRepositoryService>())
                        {
                            IFileRepositoryService service = si.TryGetInstance(sessionInfo, CriptographyLevelType.Federated, TransferMode.Streamed);

                            string path = sourcePath;

                            // Comprime o arquivo antes de enviar
                            if (this.UploadCompression == CompressionBehavior.Compress ||
                                this.UploadCompression == CompressionBehavior.DeliverCompressed)
                            {
                                using (ZipFile zipFile = new ZipFile())
                                {
                                    path = Path.GetTempFileName();
                                    if (File.Exists(path)) File.Delete(path);

                                    zipFile.AddFile(sourcePath, "");
                                    zipFile.Save(path);
                                }
                            }

                            using (FileStream readStream = new FileStream(path, FileMode.Open, FileAccess.Read))
                            {
                                service.UploadFile(new FileInfoRequest()
                                {
                                    ProcessName = processName,
                                    FileName = destFileName,
                                    CompressionBehavior = (Sinacor.Infra.UI.FileTransfer.Client.Infra.FileTransfer.FileRepositoryService.CompressionBehavior)this.UploadCompression,
                                    DataStream = readStream
                                });
                            }
                        }
                    }
                    else
                        throw new NullReferenceException("SessionInformation is null.");
                }
                else
                    throw new NullReferenceException("Session is null.");
            }
            catch (Exception ex)
            {
                throw new FileRepositoryCommunicationException("An exception ocurred uploading the file to server. See the inner exceptions for more details.", ex);
            }
        }



        /// <summary>
        /// Método responsável por efetuar o Download do arquivo
        /// </summary>
        /// <param name="processName">Nome do processo</param>
        /// <param name="destPath">Caminho completo do arquivo a ser gravado no client</param>
        public void DownloadFile(string processName, string destPath)
        {
            if (string.IsNullOrEmpty(processName))
                throw new ArgumentNullException("processName");

            if (string.IsNullOrEmpty(destPath))
                throw new ArgumentNullException("destPath");

            this.DownloadFile(processName, Path.GetFileName(destPath), destPath);
        }

        /// <summary>
        /// Método responsável por efetuar o Download do arquivo
        /// </summary>
        /// <param name="processName">Nome do processo</param>
        /// <param name="sourceFileName">Nome do arquivo a ser baixado</param>
        /// <param name="destPath">Caminho completo do arquivo a ser gravado no client</param>
        public void DownloadFile(string processName, string sourceFileName, string destPath)
        {
            if (string.IsNullOrEmpty(processName))
                throw new ArgumentNullException("processName");

            if (string.IsNullOrEmpty(sourceFileName))
                throw new ArgumentNullException("sourceFileName");

            if (string.IsNullOrEmpty(destPath))
                throw new ArgumentNullException("destPath");

            try
            {
                Session.Session session = Session.SessionManager.GetInstance().GetCurrentSession();

                if (session != null)
                {
                    SessionInformation sessionInfo = session.GenerateSessionInformation();

                    if (sessionInfo != null)
                    {
                        FileInfoResponse fileInfoResponse;
                        const int bufferSize = 20480;
                        byte[] buffer = new byte[bufferSize];
                        int bytes = 0;

                        using (ServiceInstance<IFileRepositoryService> si = new ServiceInstance<IFileRepositoryService>())
                        {
                            IFileRepositoryService service = si.TryGetInstance(sessionInfo, CriptographyLevelType.Federated, TransferMode.Streamed);

                            fileInfoResponse = service.DownloadFile(new DownloadRequest()
                            {
                                ProcessName = processName,
                                CompressionBehavior = (Sinacor.Infra.UI.FileTransfer.Client.Infra.FileTransfer.FileRepositoryService.CompressionBehavior)this.DownloadCompression,
                                VirtualPath = sourceFileName
                            });

                            string path = destPath;

                            // Salva o arquivo compactado em pasta temporária
                            if (this.DownloadCompression == CompressionBehavior.Compress)
                            {
                                path = Path.GetTempFileName();
                            }
                            else if (this.DownloadCompression == CompressionBehavior.DeliverCompressed)
                            {
                                path = Path.ChangeExtension(destPath, "zip");
                            }

                            if (File.Exists(path)) File.Delete(path);

                            using (FileStream writeStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write))
                            {
                                while ((bytes = fileInfoResponse.DataStream.Read(buffer, 0, bufferSize)) > 0)
                                {
                                    writeStream.Write(buffer, 0, bytes);
                                }
                            }

                            // Descompacta o arquivo para pasta de destino
                            if (this.DownloadCompression == CompressionBehavior.Compress)
                            {
                                if (File.Exists(destPath)) File.Delete(destPath);

                                using (ZipFile zipFile = ZipFile.Read(path))
                                {
                                    zipFile[0].Extract(Path.GetTempPath(), ExtractExistingFileAction.OverwriteSilently);
                                    File.Move(Path.Combine(Path.GetTempPath(), zipFile[0].FileName), destPath);
                                }
                            }
                        }
                    }
                    else
                        throw new NullReferenceException("SessionInformation is null.");
                }
                else
                    throw new NullReferenceException("Session is null.");
            }
            catch (Exception ex)
            {
                throw new FileRepositoryCommunicationException("An exception ocurred downloading the file from server. See the inner exceptions for more details.", ex);
            }
        }

        /// <summary>
        /// Método responsável por efetuar o Exclusão do arquivo
        /// </summary>
        /// <param name="processName">Nome do processo</param>
        /// <param name="folderLocation">Localização da pasta do arquivo</param>
        /// <param name="fileName">Nome do arquivo</param>
        public void DeleteFile(string processName, FolderLocation folderLocation, string fileName)
        {
            if (string.IsNullOrEmpty(processName))
                throw new ArgumentNullException("processName");

            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentNullException("fileName");

            try
            {
                Session.Session session = Session.SessionManager.GetInstance().GetCurrentSession();

                if (session != null)
                {
                    SessionInformation sessionInfo = session.GenerateSessionInformation();

                    if (sessionInfo != null)
                    {
                        using (ServiceInstance<IFileRepositoryService> si = new ServiceInstance<IFileRepositoryService>())
                        {
                            IFileRepositoryService service = si.TryGetInstance(sessionInfo, CriptographyLevelType.Federated, TransferMode.Streamed);

                            service.DeleteFile(new StorageFileInfoRequest()
                            {
                                ProcessName = processName,
                                VirtualPath = fileName,
                                FolderLocation = (Sinacor.Infra.UI.FileTransfer.Client.Infra.FileTransfer.FileRepositoryService.FolderLocation)folderLocation
                            });
                        }
                    }
                    else
                        throw new NullReferenceException("SessionInformation is null.");
                }
                else
                    throw new NullReferenceException("Session is null.");
            }
            catch (Exception ex)
            {
                throw new FileRepositoryCommunicationException("An exception ocurred when to delete file of the server. See the inner exceptions for more details.", ex);
            }
        }

        /// <summary>
        /// Método responsável por efetuar o Listagem dos arquivo
        /// </summary>
        /// <param name="processName">Nome do processo</param>
        /// <param name="folderLocation">Localização da pasta do arquivo</param>
        public List<FileInfoModel> ListFiles(string processName, FolderLocation folderLocation)
        {
            if (string.IsNullOrEmpty(processName))
                throw new ArgumentNullException("processName");

            List<FileInfoModel> fileReturn = new List<FileInfoModel>();

            try
            {
                Session.Session session = Session.SessionManager.GetInstance().GetCurrentSession();

                if (session != null)
                {
                    SessionInformation sessionInfo = session.GenerateSessionInformation();

                    if (sessionInfo != null)
                    {
                        List<StorageFileInfoResponse> files;

                        using (ServiceInstance<IFileRepositoryService> si = new ServiceInstance<IFileRepositoryService>())
                        {
                            IFileRepositoryService service = si.TryGetInstance(sessionInfo, CriptographyLevelType.Federated, TransferMode.Streamed);

                            files = service.ListFiles(new StorageFileInfoRequest()
                            {
                                ProcessName = processName,
                                FolderLocation = (Sinacor.Infra.UI.FileTransfer.Client.Infra.FileTransfer.FileRepositoryService.FolderLocation)folderLocation
                            });

                            foreach (StorageFileInfoResponse item in files)
                            {
                                fileReturn.Add(new FileInfoModel() { FileName = item.VirtualPath, Size = item.Size, ProcessName = processName });
                            }
                        }
                    }
                    else
                        throw new NullReferenceException("SessionInformation is null.");
                }
                else
                    throw new NullReferenceException("Session is null.");
            }
            catch (Exception ex)
            {
                throw new FileRepositoryCommunicationException("An exception ocurred when to list files of the server. See the inner exceptions for more details.", ex);
            }

            return fileReturn;
        }
    }
}
